/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.pivotcontrib.components.skin;

import org.apache.pivot.collections.Sequence;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.Dimensions;
import org.apache.pivot.wtk.skin.ContainerSkin;

import org.pivotcontrib.components.TilePane;
import org.pivotcontrib.components.TilePaneAttributeListener;
import org.pivotcontrib.components.TilePaneListener;

/**
 * Column pane skin.
 */
public class TilePaneSkin extends ContainerSkin implements TilePane.Skin,
    TilePaneListener, TilePaneAttributeListener {
    private int horizontalSpacing = 10;
    private int verticalSpacing = 10;

    // TODO Move to property of the column pane
    private static final int COLUMN_WIDTH = 155;

    @Override
    public void install(Component component) {
        super.install(component);

        TilePane tilePane = (TilePane)component;
        tilePane.getTilePaneListeners().add(this);
        tilePane.getTilePaneAttributeListeners().add(this);
    }

    @Override
    public int getPreferredWidth(int height) {
        TilePane tilePane = (TilePane)getComponent();
        TilePane.TileSequence tiles = tilePane.getTiles();

        // Determine the rightmost edge of the components (with spanning)
        int preferredWidth = 0;

        for (TilePane.Tile tile : tiles) {
            int column = tile.getColumn();
            Component component = tile.getComponent();
            int columnSpan = TilePane.getColumnSpan(component);

            int x = column * (COLUMN_WIDTH + horizontalSpacing);
            int width = columnSpan * COLUMN_WIDTH;
            if (columnSpan > 1) {
                width += (columnSpan - 1) * horizontalSpacing;
            }

            preferredWidth = Math.max(preferredWidth, x + width);
        }

        return preferredWidth;
    }

    @Override
    public int getPreferredHeight(int width) {
        TilePane tilePane = (TilePane)getComponent();
        TilePane.TileSequence tiles = tilePane.getTiles();

        int[] heights = new int[getMaximumColumn() + 1];

        for (TilePane.Tile tile : tiles) {
            int column = tile.getColumn();
            Component component = tile.getComponent();

            int columnSpan = TilePane.getColumnSpan(component);
            int y = 0;

            // Calculate the y that is needed to avoid intersection
            for (int i = 0; i < columnSpan; i++) {
                y = Math.max(y, heights[column + i]);
            }

            int componentWidth = columnSpan * COLUMN_WIDTH;
            if (columnSpan > 1) {
                componentWidth += (columnSpan - 1) * horizontalSpacing;
            }

            int componentPreferredHeight = component.getPreferredHeight(componentWidth);

            // Update the heights array
            for (int i = 0; i < columnSpan; i++) {
                heights[column + i] = y + componentPreferredHeight + verticalSpacing;
            }
        }

        // Preferred height is the maximum preferred height of the columns
        int preferredHeight = 0;

        for (int i = 0; i < heights.length; i++) {
            preferredHeight = Math.max(preferredHeight, heights[i] - verticalSpacing);
        }

        return preferredHeight;
    }

    @Override
    public void layout() {
        TilePane tilePane = (TilePane)getComponent();
        TilePane.TileSequence tiles = tilePane.getTiles();

        int[] heights = new int[getMaximumColumn() + 1];

        for (TilePane.Tile tile : tiles) {
            int column = tile.getColumn();
            Component component = tile.getComponent();

            int columnSpan = TilePane.getColumnSpan(component);
            int x = column * (COLUMN_WIDTH + horizontalSpacing);
            int y = 0;

            // Calculate the y that is needed to avoid intersection
            for (int i = 0; i < columnSpan; i++) {
                y = Math.max(y, heights[column + i]);
            }

            int width = columnSpan * COLUMN_WIDTH;
            if (columnSpan > 1) {
                width += (columnSpan - 1) * horizontalSpacing;
            }

            component.setSize(width, component.getPreferredHeight(width));
            component.setLocation(x, y);

            // Update the heights array
            for (int i = 0; i < columnSpan; i++) {
                heights[column + i] = y + component.getHeight() + verticalSpacing;
            }
        }
    }

    /**
     * Gets the spacing that will be applied in between the column pane's
     * columns during layout.
     */
    public int getHorizontalSpacing() {
        return horizontalSpacing;
    }

    /**
     * Sets the spacing that will be applied in between the column pane's
     * columns during layout.
     */
    public void setHorizontalSpacing(int horizontalSpacing) {
        if (horizontalSpacing < 0) {
            throw new IllegalArgumentException("horizontalSpacing is negative");
        }

        this.horizontalSpacing = horizontalSpacing;
        invalidateComponent();
    }

    /**
     * Gets the spacing that will be applied in between the column pane's
     * components during layout.
     */
    public int getVerticalSpacing() {
        return verticalSpacing;
    }

    /**
     * Sets the spacing that will be applied in between the column pane's
     * components during layout.
     */
    public void setVerticalSpacing(int verticalSpacing) {
        if (verticalSpacing < 0) {
            throw new IllegalArgumentException("verticalSpacing is negative");
        }

        this.verticalSpacing = verticalSpacing;
        invalidateComponent();
    }

    private int getMaximumColumn() {
        TilePane tilePane = (TilePane)getComponent();
        TilePane.TileSequence tiles = tilePane.getTiles();

        int maximumColumn = 0;

        for (TilePane.Tile tile : tiles) {
            int column = tile.getColumn();
            Component component = tile.getComponent();
            int columnSpan = TilePane.getColumnSpan(component);

            maximumColumn = Math.max(maximumColumn, column + columnSpan - 1);
        }

        return maximumColumn;
    }

    // TilePane.Skin methods

    @Override
    public int getColumnAt(int x) {
        int columnIndex = x / (COLUMN_WIDTH + horizontalSpacing);
        columnIndex = Math.min(Math.max(columnIndex, 0), getMaximumColumn() + 1);
        return columnIndex;
    }

    @Override
    public int getInsertionColumn(int x) {
        return getColumnAt(x + COLUMN_WIDTH / 2);
    }

    @Override
    public int getInsertionIndex(int column, int y, int columnSpan) {
        TilePane tilePane = (TilePane)getComponent();
        TilePane.TileSequence tiles = tilePane.getTiles();

        int[] heights = new int[getMaximumColumn() + 1];

        for (int index = 0, n = tiles.getLength(); index < n; index++) {
            TilePane.Tile tile = tiles.get(index);
            int tileColumn = tile.getColumn();
            Component tileComponent = tile.getComponent();

            int tileColumnSpan = TilePane.getColumnSpan(tileComponent);
            int tileHeight = tileComponent.getHeight();
            int tileY = 0;

            // Calculate the tileY that is needed to avoid intersection
            for (int i = 0; i < tileColumnSpan; i++) {
                tileY = Math.max(tileY, heights[tileColumn + i]);
            }

            // Test if we should insert before this tile
            if (column + columnSpan - 1 >= tileColumn
                && column <= tileColumn + tileColumnSpan - 1
                && y <= tileY + tileHeight / 2) {
                return index;
            }

            // Update the heights array
            for (int i = 0; i < tileColumnSpan; i++) {
                heights[tileColumn + i] = tileY + tileHeight + verticalSpacing;
            }
        }

        return tiles.getLength();
    }

    // TilePaneListener methods

    @Override
    public void tileInserted(TilePane tilePane, int index) {
        invalidateComponent();
    }

    @Override
    public void tilesRemoved(TilePane tilePane, int index, Sequence<TilePane.Tile> removed) {
        invalidateComponent();
    }

    // TilePaneAttribute events

    @Override
    public void columnSpanChanged(TilePane tilePane, Component component, int previousColumnSpan) {
        invalidateComponent();
    }
}
